package com.ease.util {

   import mx.utils.StringUtil;

   /**
    * Utility functions for working with IP addresses.
    */
   public class IpUtil {

      /**
      * IPv4 degrees.
      */
      private static const IPV4_DEGREE_A:Number = 1;
      private static const IPV4_DEGREE_B:Number = 2;
      private static const IPV4_DEGREE_C:Number = 3;

      private static const IPV4_DEGREE_A_START:String = "0,0,0,0";
      private static const IPV4_DEGREE_A_END:String = "127,255,255,255";
      private static const IPV4_DEGREE_B_START:String = "128,0,0,0";
      private static const IPV4_DEGREE_B_END:String = "191,255,255,255";
      private static const IPV4_DEGREE_C_START:String = "192,0,0,0";
      private static const IPV4_DEGREE_C_END:String = "223,255,255,255";

      /**
       * An IPv4 octet. Used for validating IPv4 addresses.
       */
      private static const IPV4_OCTET:String = "(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)";

      /**
       * Regular expression for an IPv4 address as four octets separated by a dot.
       */
      private static const IPV4_REGEXP:String =
         "^(" + IPV4_OCTET + "\\.){3}" + IPV4_OCTET + "$";

      /**
       * An IPv6 group of 1-4 hexadecimal numbers. Used for validating IPv6 addresses.
       */
      private static const IPV6_GROUP:String = "[0-9a-fA-F]{1,4}";

      /**
       * Regular expression for IPv6 address as eight hexadecimal groups
       * separated by a colon (preferred form).
       */
      private static const IPV6_REGEXP:String =
         "^(" + IPV6_GROUP + ":){7}" + IPV6_GROUP + "$";

      /**
       * Checks if a string is a valid IPv4 address.
       *
       * @example
       *
       * <listing version="3.0">
       * var result:Boolean;
       * result = IpUtil.isIpv4("255.255.254.0");   // true
       * result = IpUtil.isIpv4("0.00.000.001");    // true
       * result = IpUtil.isIpv4("255.255.255.256"); // false
       * result = IpUtil.isIpv4("1.2.3.4.5");       // false
       * result = IpUtil.isIpv4("1.2 . 3 .4");      // false
       * result = IpUtil.isIpv4(null);              // false
       * </listing>
       */
      public static function isIpv4(value:String):Boolean {
         var ip:IpAddress = parseIpv4(value);
         return ip != null;
      }

      /**
       * Tries to parse a string as an IPv4 address.
       *
       * @return
       *       Returns an <code>IpAddress</code> if the string is
       *       an IPv4 address, otherwise returns null.
       *
       * @example
       *
       * "10.20.254.0"     -> [10, 20, 254, 0]
       * "0.00.000.001"    -> [0, 0, 0, 1]
       * "255.255.255.256" -> null
       */
      public static function parseIpv4(value:String):IpAddress {
         if (value == null) {
            return null;
         }
         value = StringUtil.trim(value);
         var regExp:RegExp = new RegExp(IPV4_REGEXP);
         if (!regExp.test(value)) {
            return null;
         }
         return new IpAddress(getBytesIpv4(value));
      }

      /**
       * Checks if a string is a valid IPv6 address.
       * Conforms to IPv6 addressing RFC 2373:
       * http://www.faqs.org/rfcs/rfc2373.html
       *
       * @example
       *
       * <listing version="3.0">
       * var result:Boolean;
       * result = IpUtil.isIpv6("1080:0:0:0:8:800:200C:417A");   // true
       * result = IpUtil.isIpv6("1080::8:800:200C:417A");        // true
       * result = IpUtil.isIpv6("::");                           // true
       * result = IpUtil.isIpv6("0:0:0:0:0:FFFF:129.144.52.38"); // true
       * result = IpUtil.isIpv6("::FFFF:129.144.52.38");         // true
       * result = IpUtil.isIpv6("1:2:3:4::5:6:7:8");             // false
       * result = IpUtil.isIpv6(null);                           // false
       * </listing>
       */
      public static function isIpv6(value:String):Boolean {
         var ip:IpAddress = parseIpv6(value);
         return ip != null;
      }

      /**
       * Tries to parse a string as an IPv6 address.
       *
       * @return
       *       Returns an <code>IpAddress</code> if the string is
       *       an IPv6 address, otherwise returns null.
       *
       * @example
       *
       * "1080:0:0:0:8:800:200C:417A" ->
       * [16, 128, 0, 0, 0, 0, 0, 0, 0, 8, 8, 0, 32, 12, 65, 122]
       *
       * "::1" -> [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]
       *
       * "1:2:3:4::5:6:7:8" -> null
       */
      public static function parseIpv6(value:String):IpAddress {
         if (value == null) {
            return null;
         }
         value = StringUtil.trim(value);

         // Try to match preferred form.
         var regExp:RegExp = new RegExp(IPV6_REGEXP);
         if (regExp.test(value)) {
            return new IpAddress(getBytesIpv6(value));
         }

         // Check for spaces within the string
         if (value.indexOf(" ") != -1) {
            return null;
         }

         // Process mixed form x:x:x:x:x:x:d.d.d.d
         value = normalizeMixedIpv6(value);

         // Process compressed form x:x::x:x:x
         value = normalizeCompressedIpv6(value);

         // Again try to match preferred form x:x:x:x:x:x:x:x
         if (regExp.test(value)) {
            return new IpAddress(getBytesIpv6(value));
         }

         return null;
      }

      /**
       * Given an IPv4 address, return the correct string representation
       *
       * @param ip
       *       An IPv4 address
       *
       * @return
       *       The string representation of the ip address
       */
      public static function formatIPv4(ip:IpAddress):String {
         if (ip.bytes.length != 4) {
            return "";
         }
         return ip.bytes.join(".");
      }

      /**
       * Given an IPv6 address, return the correct string representation
       *
       * @param ip
       *       An IPv6 address
       *
       * @return
       *       The string representation of the ip address
       */
      public static function formatIPv6(ip:IpAddress):String {
         if (ip.bytes.length != 16) {
            return "";
         }
         return [parseIpv6BytesToHex(ip.bytes[0], ip.bytes[1]),
            parseIpv6BytesToHex(ip.bytes[2], ip.bytes[3]),
            parseIpv6BytesToHex(ip.bytes[4], ip.bytes[5]),
            parseIpv6BytesToHex(ip.bytes[6], ip.bytes[7]),
            parseIpv6BytesToHex(ip.bytes[8], ip.bytes[9]),
            parseIpv6BytesToHex(ip.bytes[10], ip.bytes[11]),
            parseIpv6BytesToHex(ip.bytes[12], ip.bytes[13]),
            parseIpv6BytesToHex(ip.bytes[14], ip.bytes[15])].join(":");
      }

      /**
       * Assumes the input is an IPv4 address string.
       * Returns a byte array with 4 elements.
       *
       * @param value
       *       An IPv4 string, e.g. "10.20.62.0"
       */
      private static function getBytesIpv4(value:String):Array {
         var parts:Array = value.split(".");
         if (parts.length != 4) {
            return null;
         }
         var bytes:Array = [];
         for (var i:uint = 0; i < 4; ++i) {
            var byte:uint = uint(parts[i]);
            bytes.push(byte);
         }
         return bytes;
      }

      /**
       * Assumes the input is an IPv6 address string in preferred form,
       * i.e. 8 parts of 1-4 hexadecimal digits separated by colon.
       * Returns a byte array with 16 elements.
       *
       * @param value
       *       An IPv6 string in preferred form,
       *       e.g. "1080:0:0:0:8:800:200C:417A"
       */
      private static function getBytesIpv6(value:String):Array {
         var parts:Array = value.split(":");
         if (parts.length != 8) {
            return null;
         }
         var bytes:Array = [];
         for (var i:uint = 0; i < 8; ++i) {
            var field:uint = parseInt(parts[i], 16);
            var byte1:uint = ((field & 0xFF00) >> 8);
            var byte2:uint =  (field & 0x00FF);
            bytes.push(byte1);
            bytes.push(byte2);
         }
         return bytes;
      }

      /**
       * Function that will convert a segment in an IPv6 address given as an integer to an
       * array of two byte values.
       *
       * @param value
       *       The integer representing one of the eight segments in an IPv6 address.
       */
      public static function parseIpv6IntToBytes(value:uint):Array {
         var byte1:uint = ((value & 0xFF00) >> 8);
         var byte2:uint =  (value & 0x00FF);
         return [byte1, byte2];
      }

      /**
       * Function that will convert a segment in an IPv6 address given as a string to an
       * unsigned integer value. If the value cannot be parsed to an integer or if the
       * value has more then 4 characters, <code>NaN</code> is returned
       *
       * @param value
       *       The string representing one of the eight groups in an IPv6 address. It has a
       *       length between zero and four characters.
       */
      public static function parseIpv6HexToInt(value:String):Number {
         if (value == null || value.length > 4) {
            return NaN;
         }
         return parseInt(value, 16);
      }

      /**
       * Function that will convert a segment in an IPv6 address given as a string to an
       * array of two byte values.
       *
       * @param value
       *       The string representing one of the eight segments in an IPv6 address. It
       *       has a length between zero and four characters.
       */
      public static function parseIpv6HexToBytes(value:String):Array {
         if (value.length > 4) {
            return null;
         }
         var group:Number = parseIpv6HexToInt(value);
         if (isNaN(group)) {
            return null;
         }
         return parseIpv6IntToBytes(uint(group));
      }

      /**
       * Function that will convert a segment in an IPv6 address given as an integer to a
       * string representing the hex value of the integer.
       *
       * @param value
       *       The numeric value of a segment in an IPv6 address.
       */
      public static function parseIpv6IntToHex(value:Number):String {
         return value.toString(16);
      }

      /**
       * Function that will convert a segment in an IPv6 address, given as two
       * bytes to a string representing the hex value of the combined input.
       *
       * @param byte1
       *       The upper 8 bits of the value
       *
       * @param byte2
       *       The lower 8 bits of the value
       *
       */
      public static function parseIpv6BytesToHex(byte1:uint, byte2:uint):String {
         var group:uint = ((byte1 & 0x00FF) << 8) | (byte2 & 0x00FF);
         return parseIpv6IntToHex(group);
      }

      /**
       * Given a string that represents a compressed IPv6 address,
       * returns the address in preferred form.
       * All compressed groups are replaced with "0000".
       * Assumes that the input is not in mixed form (i.e. that
       * <code>normalizeMixedIpv6</code> has been called previously).
       * Compressed form is indicated by "::".
       *
       * @example
       *
       * ::1 becomes
       * 0000:0000:0000:0000:0000:0000:0000:1
       *
       * ff:ee:dd:cc::1:2 becomes
       * ff:ee:dd:cc:0000:0000:1:2
       */
      private static function normalizeCompressedIpv6(value:String):String {
         var doubleColonPos:int = value.indexOf("::");
         if (doubleColonPos == -1) {
            return value; // Already normalized
         }

         var prefix:String = value.substr(0, doubleColonPos);
         var suffix:String = value.substr(doubleColonPos + 2);

         // Count groups in prefix and suffix
         var groupCount:int = 0;
         if (prefix.length > 0) {
            groupCount += prefix.split(":").length;
         }
         if (suffix.length > 0) {
            groupCount += suffix.split(":").length;
         }
         var compressedGroups:int = 8 - groupCount;
         if (compressedGroups < 1) {
            return ""; // Not a valid IPv6 address
         }

         var result:String;
         if (prefix.length == 0) {
            result = "";
         } else {
            result = prefix + ":";
         }
         for (var i:int = 0; i < compressedGroups; ++i) {
            result += "0000:";
         }
         if (suffix.length == 0) {
            // Remove the trailing colon
            result = result.substr(0, result.length - 1);
         } else {
            result += suffix;
         }
         return result;
      }

      /**
       * Given a string that represents a mixed IPv6 address,
       * returns the address with only hexadecimal fields.
       * Mixed form ends with d.d.d.d and the decimal values
       * d.d.d.d are replaced by two hexadecimal groups x:x
       *
       * @example
       *
       * f:e:d:c:b:a:255.0.0.15 becomes
       * f:e:d:c:b:a:ff00:000f
       *
       * ::255.255.255.255 becomes
       * ::ffff:ffff
       */
      private static function normalizeMixedIpv6(value:String):String {
         var firstDotPos:int = value.indexOf(".");
         if (firstDotPos == -1) {
            return value; // Already normalized
         }

         var lastColonPos:int = value.lastIndexOf(":");
         if (lastColonPos == -1 || firstDotPos < lastColonPos) {
            return ""; // Not an IPv6 address
         }

         // Test that the last part is an IPv4 address
         var ipv4Part:String = value.substr(lastColonPos + 1);
         var regExp:RegExp = new RegExp(IPV4_REGEXP);
         if (!regExp.test(ipv4Part)) {
            return "";
         }

         var result:String = value.substring(0, lastColonPos + 1);
         // Add the four decimal numbers as two hexadecimal fields separated by a colon
         // E.g. 255.0.0.15 -> ff00:000f
         var ipv4Octets:Array = ipv4Part.split(".");
         for (var i:int = 0; i < 4; ++i) {
            if (i == 2) {
               result += ":";
            }
            var octet:Number = Number(ipv4Octets[i]);
            var hex:String = parseIpv6IntToHex(octet);
            if (hex.length == 1) {
               hex = "0" + hex;
            }
            result += hex;
         }
         return result;
      }

      /**
       * Adds a number to an IP address and returns the result
       * as an IP address.
       *
       * @param ip
       *       IP address as a uint array.
       * @param number
       *       The 32-bit number to add to the IP address.
       * @return
       *       An IP address as a uint array or null if
       *       the addition results in an overflow.
       */
      public static function add(ip:IpAddress, number:uint):IpAddress {
         if (number == 0) {
            return ip;
         }
         var ipBytes:Array = ip.bytes;
         var numElements:uint = ipBytes.length;
         var result:Array = new Array(numElements);
         var carry:uint = 0;
         for (var i:uint = 0; i < numElements; ++i) {
            var index:uint = numElements - 1 - i; // Process bytes from right to left
            var current:uint = ipBytes[index];
            var part:uint = current + carry;
            if (i < 4) {
               // This is a digit-wise sum (with base 256) of the last 4 bytes:
               // ip:      |len-1|...|3|2|1|0|  (where len is 4 or 16)
               // number:            |3|2|1|0|
               var add:uint = (number >> i * 8) & 0xFF;
               part += add;
            }
            // If the result part is greater than 0xFF there is a carry to the next byte
            result[index] = part & 0xFF;
            carry = part / 0x100;
         }
         if (carry != 0) {
            // If carry is not 0, there was an overflow
            return null;
         }
         return new IpAddress(result);
      }

      /**
       * Given the number of bits desired in a subnet return the correct IPv4 network mask.
       *
       * @param networkBits
       *       The number of network bits can be between 0 and 32
       *
       * @return
       *       The IPv4 network mask for the desired number of network bits
       */
      public static function getIPv4Netmask(networkBits:int):IpAddress {
         Diagnostics.assert((networkBits <= 32) && (networkBits >= 0),
            "IPv4 network bits must be in the range 0 - 32");
         if (networkBits > 32) {
            networkBits = 32;
         } else if (networkBits < 0) {
            networkBits = 0;
         }
         return getNetmask(networkBits, 1);
      }

      /**
       * Given the number of bits desired in a subnet return the correct IPv6 network mask.
       *
       * @param networkBits
       *       The number of network bits can be between 0 and 128
       *
       * @return
       *       The IPv6 network mask for the desired number of network bits
       */
      public static function getIPv6Netmask(networkBits:int):IpAddress {
         Diagnostics.assert((networkBits <= 128) && (networkBits >= 0),
            "IPv6 network bits must be in the range 0 - 128");
         if (networkBits > 128) {
            networkBits = 128;
         } else if (networkBits < 0) {
            networkBits = 0;
         }
         return getNetmask(networkBits, 4);
      }

      /**
       * Helper function for calculating the network mask
       */
      private static function getNetmask(networkBits:int, multiplier:int):IpAddress {
         var addressBytes:Array = new Array(4 * multiplier);
         for (var i:int = 0; i < multiplier; ++i) {
            var mask:uint = 0;
            var length:int = Math.max(0, Math.min(32, networkBits - (32 * i)));
            for (var j:int = 0; j < length; ++j) {
               mask |= (1 << (31 - j));
            }
            addressBytes[multiplier *  i]      = (mask >> 24) & 0xFF;
            addressBytes[(multiplier * i) + 1] = (mask >> 16) & 0xFF;
            addressBytes[(multiplier * i) + 2] = (mask >> 8) & 0xFF;
            addressBytes[(multiplier * i) + 3] =  mask & 0xFF;
         }
         return new IpAddress(addressBytes);
      }

      /**
       * Given a start IP address and the network mask we can calculate the maximum
       * IP address.
       *
       * @param start
       *       The IP address were we start the calculation
       *
       * @param netmask
       *       The network mask address
       *
       * @return
       *       The maximum IP address based on the start IP address and the network mask.
       */
      public static function getMaxAddress(
         start:IpAddress,
         netmask:IpAddress):IpAddress {
         var result:Array = [];
         var startBytes:Array = start.bytes;
         var netmaskBytes:Array = netmask.bytes;
         // Invert netmask and OR it with start IP
         for (var i:uint = 0; i < startBytes.length; ++i) {
            var inverted:uint = ~netmaskBytes[i] & 0xFF;
            result[i] = inverted | startBytes[i];
         }
         // Exclude the last address for IPv4 since it is reserved for broadcast
         if (start.isIpv4) {
            result[3] &= 0xFE;
         }
         return new IpAddress(result);
      }

      public static function getIPv4Degree(ip:IpAddress):Number {
         if(ip){
            if(ip.equals(parseIpv4(IPV4_DEGREE_A_START)) || ip.equals(parseIpv4(IPV4_DEGREE_A_END))
               && (!ip.lessThan(parseIpv4(IPV4_DEGREE_A_START)) && ip.lessThan(parseIpv4(IPV4_DEGREE_A_END)))){
               return IPV4_DEGREE_A;
            }
            else if(ip.equals(parseIpv4(IPV4_DEGREE_B_START)) || ip.equals(parseIpv4(IPV4_DEGREE_B_END))
               && (!ip.lessThan(parseIpv4(IPV4_DEGREE_B_START)) && ip.lessThan(parseIpv4(IPV4_DEGREE_B_END)))){
               return IPV4_DEGREE_B;
            }
            else if(ip.equals(parseIpv4(IPV4_DEGREE_C_START)) || ip.equals(parseIpv4(IPV4_DEGREE_C_END))
               && (!ip.lessThan(parseIpv4(IPV4_DEGREE_C_START)) && ip.lessThan(parseIpv4(IPV4_DEGREE_C_END)))){
               return IPV4_DEGREE_C;
            }
         }
         return 0;
      }

      public static function getString(ip:IpAddress):String {
         var retStr:String = null;
         if (ip.bytes) {
            retStr = new String();
            for each (var partIp:String in ip.bytes) {
               retStr += (partIp + ".");
            }
            retStr = retStr.substring(0,retStr.length - 1);
         }
         return retStr;
      }

      public static function formatBinaryIp(ip:IpAddress):IpAddress {
         var binaryIp:IpAddress = null;
         if (ip) {
			   binaryIp = new IpAddress(getBinaryArray(ip.bytes));
         }
         return binaryIp;
      }

     public static function formatDecimalIp(ip:IpAddress):IpAddress {
         var binaryIp:IpAddress = null;
         if (ip) {
			binaryIp = new IpAddress(getDecimalArray(ip.bytes));
         }
         return binaryIp;
      }

      public static function getBinaryArray(array:Array):Array {
         var binaryArray:Array = null;
         if (array) {
            binaryArray = new Array();
            for each (var element:Number in array) {
               if (0 == element) {
                  binaryArray[binaryArray.length] = "00000000";
               }
               else {
                  binaryArray[binaryArray.length] = element.toString(2);
               }
            }
         }
         return binaryArray;
      }

      public static function getDecimalArray(array:Array):Array {
         var decimalArray:Array = null;
         if (array) {
            decimalArray = new Array();
            for each (var partIp:String in array) {
               decimalArray[decimalArray.length] = parseInt(partIp,2);
            }
         }
         return decimalArray;
      }

      //把掩码与IP做按位与，得到网络地址
      public static function getIPv4NetworkAddress(ipBinaryArray:Array,subNetMaskBinaryArray:Array):Array {
         var networkAddressArray:Array = new Array();
         for (var i:Number = 0 ; i < 4 ; i ++) {
            networkAddressArray[i] = ipBinaryArray[i] & subNetMaskBinaryArray[i];
         }
         return networkAddressArray;
      }
   } // class
} // package
